/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <pony/mtd/at24cxx.h>
#include <hal_i2c.h>
#include <hal_delay.h>
/* includes (standard library, system) ---------------------------------------*/
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_module.h>

#include <k_errno.h>

/* defines -------------------------------------------------------------------*/

#ifndef CONFIG_AT24CXX_ADDR
#define CONFIG_AT24CXX_ADDR 0xA0
#endif

#ifndef CONFIG_AT24CXX_I2C_BUS
#define CONFIG_AT24CXX_I2C_BUS "i2c1"
#endif

/* typedefs ------------------------------------------------------------------*/

typedef struct at24cxx {
    hal_mtd_t mtd;
    hal_i2c_t *i2c;
} at24cxx_t;

/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static at24cxx_t s_at24cxx;

/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/

static inline int __at24cxx_check_block(size_t startblock, size_t nblocks)
{
    if (startblock >= AT24CXX_NPAGES || startblock + nblocks > AT24CXX_NPAGES) {
        return -1;
    }
    return 0;
}

static inline int __at24cxx_i2c_read(at24cxx_t *at24cxx, size_t pos, size_t nbytes, uint8_t *buf)
{
    return hal_i2c_read(at24cxx->i2c, CONFIG_AT24CXX_ADDR, pos, AT24CXX_ADDRSIZE, buf, nbytes);
}

/* functions -----------------------------------------------------------------*/

static int __at24cxx_i2c_write(at24cxx_t *at24cxx, size_t pos, size_t nbytes, const uint8_t *buf)
{
    int ret;

    ret = hal_i2c_write(at24cxx->i2c, CONFIG_AT24CXX_ADDR, pos, AT24CXX_ADDRSIZE, buf, nbytes);
    DELAY_MS(3);   // wait eeprom standby

    return ret;
}

static int __at24cxx_erase_block(hal_mtd_t *mtd, size_t startblock, size_t nblocks)
{
    return -ENOSYS;
}

static int __at24cxx_read_block(hal_mtd_t *mtd, size_t startblock, size_t nblocks, uint8_t *buf)
{
    if (__at24cxx_check_block(startblock, nblocks) != 0) {
        return -EINVAL;
    }

    at24cxx_t *at24cxx = container_of(mtd, at24cxx_t, mtd);

    return __at24cxx_i2c_read(at24cxx, startblock * AT24CXX_PAGESIZE, nblocks * AT24CXX_PAGESIZE,
                              buf);
}

static int __at24cxx_write_block(hal_mtd_t *mtd, size_t startblock, size_t nblocks,
                                 const uint8_t *buf)
{
    size_t i;

    if (__at24cxx_check_block(startblock, nblocks) != 0) {
        return -EINVAL;
    }

    at24cxx_t *at24cxx = container_of(mtd, at24cxx_t, mtd);

    for (i = 0; i < nblocks; i++) {
        size_t step = i * AT24CXX_PAGESIZE;
        if (__at24cxx_i2c_write(at24cxx, startblock + step, AT24CXX_PAGESIZE, buf + step) != 0) {
            return -1;
        }
    }
    return 0;
}

static int __at24cxx_erase(hal_mtd_t *mtd, size_t pos, size_t nbytes)
{
    return -ENOSYS;
}

static int __at24cxx_read(hal_mtd_t *mtd, size_t pos, size_t nbytes, uint8_t *buf)
{
    at24cxx_t *at24cxx = container_of(mtd, at24cxx_t, mtd);

    return __at24cxx_i2c_read(at24cxx, pos, nbytes, buf);
}

static int __at24cxx_write(hal_mtd_t *mtd, size_t pos, size_t nbytes, const uint8_t *buf)
{
    at24cxx_t *at24cxx = container_of(mtd, at24cxx_t, mtd);

    return __at24cxx_i2c_write(at24cxx, pos, nbytes, buf);
}

static int __at24cxx_ioctl(hal_mtd_t *mtd, int cmd, va_list valist)
{
    return -ENOSYS;
}

static const hal_mtd_methods_t at24cxx_methods = {
    .erase_block = __at24cxx_erase_block,
    .read_block = __at24cxx_read_block,
    .write_block = __at24cxx_write_block,
    .erase = __at24cxx_erase,
    .read = __at24cxx_read,
    .write = __at24cxx_write,
    .ioctl = __at24cxx_ioctl,
};

static int __at24cxx_open(hal_dev_t *dev, int oflag, va_list valist)
{
    at24cxx_t *at24cxx = (at24cxx_t *)dev;

    at24cxx->i2c = hal_i2c_open(CONFIG_AT24CXX_I2C_BUS);
    if (at24cxx->i2c == NULL) {
        return -1;
    }

    return 0;
}

static hal_dev_ops_t dev_ops = {
    .open = __at24cxx_open,
};

static int at24cxx_init(void)
{
    s_at24cxx.mtd.page_size = AT24CXX_PAGESIZE;
    s_at24cxx.mtd.total_size = AT24CXX_NR_BYTES;
    if (hal_mtd_register(&s_at24cxx.mtd, "at24cxx", &at24cxx_methods, 0)) {
        return -1;
    }
    if (hal_dev_init((hal_dev_t *)&s_at24cxx, &dev_ops) != 0) {
        return -2;
    }

    return 0;
}

MODULE_INIT(driver, at24cxx_init);
